---
title: Authentication with OAuth or OpenID providers
description: Learn how to utilize the expo-auth-session library to implement authentication with OAuth or OpenID providers.
---

import { ASSETS, Grid, GridItem, Box } from '~/ui/components/Authentication';
import { Tab, Tabs } from '~/ui/components/Tabs';

Expo can be used to login to many popular providers on Android, iOS, and web. Most of these guides utilize the pure JS [`AuthSession` API](/versions/latest/sdk/auth-session), refer to those docs for more information on the API.

Here are some **important rules** that apply to all authentication providers:

- Use `WebBrowser.maybeCompleteAuthSession()` to dismiss the web popup. If you forget to add this then the popup window will not close.
- Create redirects with `AuthSession.makeRedirectUri()` this does a lot of the heavy lifting involved with universal platform support. Behind the scenes, it uses `expo-linking`.
- Build requests using `AuthSession.useAuthRequest()`, the hook allows for async setup which means mobile browsers won't block the authentication.
- Be sure to disable the prompt until `request` is defined.
- You can only invoke `promptAsync` in user interaction on the web.
- Expo Go cannot be used for local development and testing of OAuth or OpenID Connect-enabled apps due to the inability to customize your app scheme. You can instead use a [Development Build](/develop/development-builds/introduction/), which enables an Expo Go-like development experience and supports OAuth redirects back to your app after login in a manner that works just like it would in production.

## Obtaining access tokens

Most provides in this guide use the [OAuth 2](https://oauth.net/2/) standard for secure authentication and authorization. In the authorization code grant, the identity provider returns a one-time code. This code is then exchanged for the user's access token.

Since [your application code is not a secure place to store secrets](https://reactnative.dev/docs/security#storing-sensitive-info), it is necessary to exchange the authorization code in a context (for example, your server). This will allow you to securely store and use a client secret to access the provider's token endpoint.

## Guides

**AuthSession** can be used for any OAuth or OpenID Connect provider, we've assembled guides for using the most requested services!
If you'd like to see more, you can [open a PR](https://github.com/expo/expo/edit/main/docs/pages/guides/authentication.mdx) or [vote on canny](https://expo.canny.io/feature-requests).

<Grid>
  <GridItem title="IdentityServer 4" protocol={['OAuth 2', 'OpenID']} image={ASSETS.id4} />
  <GridItem title="Asgardeo" protocol={['OAuth 2', 'OpenID']} image={ASSETS.asgardeo} />
  <GridItem title="Azure" protocol={['OAuth 2', 'OpenID']} image={ASSETS.azure} />
  <GridItem
    title="Apple"
    protocol={['iOS Only']}
    href="/versions/latest/sdk/apple-authentication"
    image={ASSETS.apple}
  />
  <GridItem
    title="Beyond Identity"
    protocol={['OAuth 2', 'OpenID']}
    image={ASSETS.beyondidentity}
  />
  <GridItem title="Calendly" protocol={['OAuth 2']} image={ASSETS.calendly} />
  <GridItem title="Cognito" protocol={['OAuth 2', 'OpenID']} image={ASSETS.cognito} />
  <GridItem title="Coinbase" protocol={['OAuth 2']} image={ASSETS.coinbase} />
  <GridItem title="Descope" protocol={['OAuth 2', 'OpenID']} image={ASSETS.descope} />
  <GridItem title="Dropbox" protocol={['OAuth 2']} image={ASSETS.dropbox} />
  <GridItem
    title="Facebook"
    protocol={['OAuth 2']}
    href="/guides/facebook-authentication/"
    image={ASSETS.facebook}
  />
  <GridItem title="Fitbit" protocol={['OAuth 2']} image={ASSETS.fitbit} />
  <GridItem
    title="Firebase Phone"
    protocol={['Recaptcha']}
    href="/versions/latest/sdk/firebase-recaptcha"
    image={ASSETS.firebase}
  />
  <GridItem title="GitHub" protocol={['OAuth 2']} image={ASSETS.github} />
  <GridItem
    title="Google"
    protocol={['OAuth 2', 'OpenID']}
    href="/guides/google-authentication/"
    image={ASSETS.google}
  />
  <GridItem title="Imgur" protocol={['OAuth 2']} image={ASSETS.imgur} />
  <GridItem title="Keycloak" protocol={['OAuth 2', 'OpenID']} image={ASSETS.keycloak} />
  <GridItem title="Logto" protocol={['OAuth 2', 'OpenID']} image={ASSETS.logto} />
  <GridItem title="Okta" protocol={['OAuth 2', 'OpenID']} image={ASSETS.okta} />
  <GridItem title="Reddit" protocol={['OAuth 2']} image={ASSETS.reddit} />
  <GridItem title="Slack" protocol={['OAuth 2']} image={ASSETS.slack} />
  <GridItem title="Spotify" protocol={['OAuth 2']} image={ASSETS.spotify} />
  <GridItem title="Strava" protocol={['OAuth 2']} image={ASSETS.strava} />
  <GridItem title="Strivacity" protocol={['OAuth 2']} image={ASSETS.strivacity} />
  <GridItem title="Twitch" protocol={['OAuth 2']} image={ASSETS.twitch} />
  <GridItem title="Twitter" protocol={['OAuth 2']} image={ASSETS.twitter} />
  <GridItem title="Uber" protocol={['OAuth 2']} image={ASSETS.uber} />

</Grid>

<Box
  name="IdentityServer 4"
  image={ASSETS.id4}
>

| Website                  | Provider | PKCE     | Auto Discovery |
| ------------------------ | -------- | -------- | -------------- |
| [More Info][c-identity4] | OpenID   | Required | Available      |

[c-identity4]: https://demo.identityserver.io/

- If `offline_access` isn't included then no refresh token will be returned.

{/* prettier-ignore */}
```tsx IdentityServer 4 Example
import * as React from 'react';
import { Button, Text, View } from 'react-native';
import * as AuthSession from 'expo-auth-session';
import * as WebBrowser from 'expo-web-browser';

/* @info <strong>Web only:</strong> This method should be invoked on the page that the auth popup gets redirected to on web, it'll ensure that authentication is completed properly. On native this does nothing. */
WebBrowser.maybeCompleteAuthSession();
/* @end */
const redirectUri = AuthSession.makeRedirectUri();

export default function App() {
  /* @info If the provider supports auto discovery then you can pass an issuer to the `useAutoDiscovery` hook to fetch the discovery document. */
  const discovery = AuthSession.useAutoDiscovery('https://demo.identityserver.io');
  /* @end */
  // Create and load an auth request
  const [request, result, promptAsync] = AuthSession.useAuthRequest(
    {
      clientId: 'native.code',
      /* @info After a user finishes authenticating, the server will redirect them to this URI. Learn more about <a href="../../guides/linking/">linking here</a>. */
      redirectUri,
      /* @end */
      scopes: ['openid', 'profile', 'email', 'offline_access'],
    },
    discovery
  );

  return (
    <View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
      <Button title="Login!" disabled={!request} onPress={() => promptAsync()} />
      {result && <Text>{JSON.stringify(result, null, 2)}</Text>}
    </View>
  );
}
```

</Box>
{/* End IdentityServer 4 */}

<Box
  name="Asgardeo"
  createUrl="https://wso2.com/asgardeo/docs/guides/#developer-guide"
  image={ASSETS.asgardeo}
>

| Website                                         | Provider | PKCE      | Auto Discovery |
| ----------------------------------------------- | -------- | --------- | -------------- |
| [Get Your Config](https://console.asgardeo.io/) | OpenID   | Supported | Available      |

- Make sure to check `Public Client` option in the console.
- Choose the intended grant in the Allowed grant types section.

{/* prettier-ignore */}
```tsx Asgardeo Auth Example
import { useState, useEffect } from 'react';
import { StyleSheet, Text, View, Button, Alert } from 'react-native';
import * as AuthSession from "expo-auth-session";
import * as WebBrowser from "expo-web-browser";
import jwtDecode from "jwt-decode";

WebBrowser.maybeCompleteAuthSession();

const redirectUri = AuthSession.makeRedirectUri();

/* @info <strong>Client ID:</strong> This can be found on the protocol tab of Asgardeo Console */
const CLIENT_ID = "YOUR_CLIENT_ID";
/* @end */

export default function App() {

    /* @info <strong>Auto Discovery URL:</strong> This can be found on the info tab of Asgardeo Console. Copy the link under `Issuer` */
    const discovery = AuthSession.useAutoDiscovery('https://api.asgardeo.io/t/<YOUR_ORG_NAME>/oauth2/token');
    /* @end */
    const [tokenResponse, setTokenResponse] = useState({});
    const [decodedIdToken, setDecodedIdToken] = useState({});

    const [request, result, promptAsync] = AuthSession.useAuthRequest(
        {
            redirectUri,
            clientId: CLIENT_ID,
            responseType: "code",
            scopes: ["openid", "profile", "email"]
        },
        discovery
    );

    const getAccessToken = () => {
      if (result?.params?.code) {
        /* @info <strong>Token Endpoint:</strong> This can be found on the info tab of Asgardeo Console. Copy the link under `Token` */
        fetch(
        /* @end */
        "https://api.asgardeo.io/t/iamapptesting/oauth2/token",
          {
            method: "POST",
            headers: {
              "Content-Type": "application/x-www-form-urlencoded"
            },
            body: `grant_type=authorization_code&code=${result?.params?.code}&redirect_uri=${redirectUri}&client_id=${CLIENT_ID}&code_verifier=${request?.codeVerifier}`
          }).then((response) => {
              return response.json();
            }).then((data) => {
              setTokenResponse(data);
              setDecodedIdToken(jwtDecode(data.id_token));
            }).catch((err) => {
              console.log(err);
            });
        }
    }

    useEffect(() => {
      (async function setResult() {
        if (result) {
          if (result.error) {
            Alert.alert(
              "Authentication error",
              result.params.error_description || "something went wrong"
            );
            return;
          }
          if (result.type === "success") {
            getAccessToken();
          }
        }
      })();
    }, [result]);


    return (
      <View style={styles.container}>
        <Button title="Login" disabled={!request} onPress={() => promptAsync()} />
        {decodedIdToken && <Text>Welcome {decodedIdToken.given_name || ""}!</Text>}
        {decodedIdToken && <Text>{decodedIdToken.email}</Text>}
        <View style={styles.accessTokenBlock}>
          decodedToken && <Text>Access Token: {tokenResponse.access_token}</Text>
        </View>
      </View>
    );
}

const styles = StyleSheet.create({
    container: {
        flex: 1,
        backgroundColor: '#fff',
        alignItems: 'center',
        justifyContent: 'center',
    },
    accessTokenBlock: {
        width: 300,
        height: 500,
        overflow: "scroll"
    }
});
```

</Box>
{/* End Asgardeo */}

<Box
  name="Azure"
  createUrl="https://docs.microsoft.com/en-us/azure/active-directory/develop/v2-overview"
  image={ASSETS.azure}
>

| Website                     | Provider | PKCE      | Auto Discovery |
| --------------------------- | -------- | --------- | -------------- |
| [Get Your Config][c-azure2] | OpenID   | Supported | Available      |

[c-azure2]: https://docs.microsoft.com/en-us/azure/active-directory/develop/v2-overview

{/* prettier-ignore */}
```tsx Azure Example
import * as React from 'react';
import * as WebBrowser from 'expo-web-browser';
import {
  exchangeCodeAsync,
  makeRedirectUri,
  useAuthRequest,
  useAutoDiscovery,
} from 'expo-auth-session';
import { Button, Text, SafeAreaView } from 'react-native';

/* @info <strong>Web only:</strong> This method should be invoked on the page that the auth popup gets redirected to on web, it'll ensure that authentication is completed properly. On native this does nothing. */
WebBrowser.maybeCompleteAuthSession();
/* @end */

export default function App() {
  // Endpoint
  const discovery = useAutoDiscovery(
    'https://login.microsoftonline.com/<TENANT_ID>/v2.0',
  );
  const redirectUri = makeRedirectUri({
    /* @info The URI <code>[scheme]://</code> to be used. If undefined, the <code>scheme</code> property of your app.json or app.config.js will be used instead. */
    scheme: undefined,
    /* @end */
    /* @info Azure requires there to be a path in your redirect URI. */
    path: 'auth',
    /* @end */
  });
  const clientId = '<CLIENT_ID>';

  // We store the JWT in here
  const [token, setToken] = React.useState<string | null>(null);

  // Request
  const [request, , promptAsync] = useAuthRequest(
    {
      clientId,
      scopes: ['openid', 'profile', 'email', 'offline_access'],
      redirectUri,
    },
    discovery,
  );

  return (
    <SafeAreaView>
      <Button
        disabled={/* @info Disable the button until the request is loaded asynchronously*/!request/* @end */}
        title="Login"
        onPress={() => {
          /* @info Prompt the user to authenticate in a user interaction or web browsers will block it. */
          promptAsync().then((codeResponse) => {
            /* @end */
            if (request && codeResponse?.type === 'success' && discovery) {
              /* @info Exchange the code to get the JWT. */
              exchangeCodeAsync(
                /* @end */
                {
                  clientId,
                  code: codeResponse.params.code,
                  /* @info Reuse the codeVerifier for PKCE */
                  extraParams: request.codeVerifier
                    ? { code_verifier: request.codeVerifier }
                    : undefined,
                  /* @end */
                  redirectUri,
                },
                discovery,
              ).then((res) => {
                setToken(res.accessToken);
              });
            }
          });
        }}
      />
      <Text>{token}</Text>
    </SafeAreaView>
  );
}
```

</Box>
{/* End Azure */}

<Box
  name="Beyond Identity"
  createUrl="https://www.beyondidentity.com/developers/signup"
  image={ASSETS.beyondidentity}
>

| Website                                                             | Provider | PKCE      | Auto Discovery |
| ------------------------------------------------------------------- | -------- | --------- | -------------- |
| [Get your config](https://www.beyondidentity.com/developers/signup) | OpenID   | Supported | Available      |

- Beyond Identity allows developers to implement strong passwordless authentication based on public-private key pairs called Universal Passkeys. All keys are cryptographically linked to the user and can be centrally managed using the Beyond Identity APIs.
- You will need a Universal Passkey before you can authenticate. See [Beyond Identity documentation](https://developer.beyondidentity.com).
- Make sure to [create a development build](/develop/development-builds/create-a-build/) and follow instructions to [install required config plugins](https://github.com/gobeyondidentity/bi-sdk-react-native/tree/main#using-expo).
- For a complete example app, see [SDK's GitHub repository](https://github.com/gobeyondidentity/bi-sdk-react-native).

<Tabs tabs={["Auth Code with automatic invocation", "Auth Code with manual invocation"]}>
<Tab>
- Set your Beyond Identity [Authenticator Config's](https://developer.beyondidentity.com/docs/v1/platform-overview/authenticator-config) Invocation Type to **Automatic**.
- If **Automatic** is selected, Beyond Identity will automatically redirect to your application using the **Invoke URL** (the App Scheme or Universal URL pointing to your application).

{/* prettier-ignore */}
```jsx Auth Code
import { useEffect } from 'react';
import { makeRedirectUri, useAuthRequest, useAutoDiscovery } from 'expo-auth-session';
import { Button } from 'react-native';
/* @info Import the Beyond Identity Embedded SDK.*/
import { Embedded } from '@beyondidentity/bi-sdk-react-native';
/* @end */

export default function App() {
  // Endpoint
  const discovery = useAutoDiscovery(
    `https://auth-${region}.beyondidentity.com/v1/tenants/${tenant_id}/realms/${realm_id}/applications/${application_id}`
  );
  // Request
  const [request, response, promptAsync] = useAuthRequest(
    {
      clientId: `${client_id}`,
      scopes: ['openid'],
      redirectUri: makeRedirectUri({
        /* @info The URI <code>[scheme]://</code> to be used. If undefined, the <code>scheme</code> property in your app.json or app.config.js is used instead. */
        scheme: 'your.app',
        /* @end */
      }),
    },
    discovery
  );

  useEffect(() => {
    const authenticate = async url => {
      // Display UI for the user to select a passwordless passkey
      const passkeys = await Embedded.getPasskeys();

      if (await Embedded.isAuthenticateUrl(url)) {
        // Pass url and a selected passkey ID into the Beyond Identity Embedded SDK authenticate function
        /* @info Parse query parameters from the 'redirectUrl' for a 'code' and then exchange that code for an access token */
        const { redirectUrl } = await Embedded.authenticate(url, passkeys[0].id);
        /* @end */
      }
    };

    /* @info The response does not need to be of type 'success' if it has a url. The state value is stored in a JWT in the url 'request' parameter, and the url will be validated through the Beyond Identity Embedded SDK. */
    if (response?.url) {
      /* @end */
      authenticate(url);
    }
  }, [response]);

  return (
    <Button
      /* @info Disable the button until the request is loaded asynchronously. */
      disabled={!request}
      /* @end */
      title="Passwordless Login"
      onPress={() => {
        /* @info Prompt the user to authenticate in a user interaction or web browsers will block it. */
        promptAsync();
        /* @end */
      }}
    />
  );
}
```

</Tab>

<Tab>
- Set your Beyond Identity [Authenticator Config's](https://developer.beyondidentity.com/docs/v1/platform-overview/authenticator-config) Invocation Type to **Manual**.
- If **Manual** is selected, an authentication URL is returned as part of a JSON response. No redirects are needed and do not require web service authentication. The result is a completely silent OAuth 2.0 authentication using Passkeys.

{/* prettier-ignore */}
```jsx Auth Code
import React from 'react';
import { Button } from 'react-native';
/* @info Import the Beyond Identity Embedded SDK.*/
import { Embedded } from '@beyondidentity/bi-sdk-react-native';
/* @end */

export default function App() {
  async function authenticate() {
    const BeyondIdentityAuthUrl = `https://auth-${region}.beyondidentity.com/v1/tenants/${tenant_od}/realms/${realm_id}/applications/${application_id}/authorize?response_type=code&client_id=${client_id}&redirect_uri=${uri_encoded_redirect_uri}&scope=openid&state=${state}&code_challenge_method=S256&code_challenge=${pkce_code_challenge}`;

    let response = await fetch(BeyondIdentityAuthUrl, {
      method: 'GET',
      headers: new Headers({
        'Content-Type': 'application/json',
      }),
    });
    const data = await response.json();

    // Display UI for the user to select a passwordless passkey
    const passkeys = await Embedded.getPasskeys();

    if (await Embedded.isAuthenticateUrl(data.authenticate_url)) {
      // Pass url and selected Passkey ID into the Beyond Identity Embedded SDK authenticate function
      /* @info Parse query parameters from the 'redirectUrl' for a 'code' and then exchange that code for an access token */
      const { redirectUrl } = await Embedded.authenticate(data.authenticate_url, passkeys[0].id);
      /* @end */
    }
  }

  return (
    <Button
      title="Passwordless Login"
      onPress={authenticate}
    />
  );
}
```

</Tab>

</Tabs>

</Box>
{/* End Beyond Identity */}

{/* Start Calendly */}

<Box
  name="Calendly"
  createUrl="https://developer.calendly.com/getting-started"
  image={ASSETS.calendly}
>
| Website                   | Provider  | PKCE      | Auto Discovery |
| ------------------------- | --------- | --------- | -------------- |
| [Get Your Config](https://developer.calendly.com/getting-started) | OAuth 2.0 | Supported | Not Available  |
- `redirectUri` requires two slashes (`://`).
- Example redirectUri:
  - Standalone / development build: `myapp://*`
  - Web: `https://yourwebsite.com/*`

{/* prettier-ignore */}
```jsx Calendly Auth Example
import * as WebBrowser from 'expo-web-browser';
import {
  makeRedirectUri,
  useAuthRequest,
  exchangeCodeAsync,
} from "expo-auth-session";
import React, { useEffect, useState } from "react";

/* @info <strong>Web only:</strong> This method should be invoked on the page that the auth popup gets redirected to on web, it'll ensure that authentication is completed properly. On native, this does nothing. */
WebBrowser.maybeCompleteAuthSession();
/* @end */

const discovery = {
  authorizationEndpoint: "https://auth.calendly.com/oauth/authorize",
  tokenEndpoint: "https://auth.calendly.com/oauth/token",
};

export default function App() {
  const [authTokens, setAuthTokens] = useState({access_token: "", refresh_token: ""});
  const [request, response, promptAsync] = useAuthRequest(
    {
      clientId: /* @info Your client id. Can be hardcoded or put it in .env file */ process.env.EXPO_PUBLIC_Client_ID /* @end */,
      usePKCE: true,
      redirectUri: makeRedirectUri({
        native: /* @info Set up scheme property in your app.json or app.config.js. For example, app.json {scheme: "myapp"} */ "myapp://" /* @end */,
      }),
    },
    discovery
  );

  useEffect(() => {
    const exchange = async (exchangeTokenReq) => {
      try {
        const exchangeTokenResponse = await exchangeCodeAsync(
          {
            clientId: process.env.EXPO_PUBLIC_Client_ID,
            code: /* @info Received code from first request*/ exchangeTokenReq /* @end */,
            redirectUri: makeRedirectUri({
              native: "myapp://",
            }),
            extraParams: {
              code_verifier: request.codeVerifier,
            },
          },
          discovery
        );
        setAuthTokens(exchangeTokenResponse);
      } catch (error) {
        console.error("error", error);
      }
    };

    if (response) {
      if (response.error) {
        console.error(
          "Authentication error",
          response.params.error_description || "something went wrong"
        );
      }
      if (response.type === "success") {
        /* @info Exchange Received Code for Access Token */
        exchange( response.params.code);
        /* @end */
      }
    }
  }, [discovery, request, response]);

  return (
  <SafeAreaView>
      <View>
        <Text>0Auth2</Text>
        <Button
          title="Connect to Calendly"
          onPress={() => {
            /* @info Prompt the user to authenticate in a user interaction or web browsers will block it. */
            promptAsync();
            /* @end */
          }}
        />
        <Text>AuthTokens: {JSON.stringify(authTokens)}</Text>
      </View>
  </SafeAreaView>
  )
}

```

</Box>
{/* End Calendly*/}

<Box
  name="Cognito"
  createUrl="https://console.aws.amazon.com/cognito/v2/idp/user-pools"
  image={ASSETS.cognito}
>

| Website                      | Provider | PKCE      | Auto Discovery |
| ---------------------------- | -------- | --------- | -------------- |
| [Get Your Config][c-cognito] | OpenID   | Supported | Not Available  |

[c-cognito]: https://console.aws.amazon.com/cognito/v2/idp/user-pools
[c-cognito-api-docs]: https://docs.aws.amazon.com/cognito/latest/developerguide/cognito-userpools-server-contract-reference.html

- Leverages the Hosted UI in Cognito ([API documentation][c-cognito-api-docs])
- Requests code after successfully authenticating, followed by exchanging code for the auth tokens (PKCE)
- The `/token` endpoint requires a `code_verifier` parameter which you can retrieve from the request before calling `exchangeCodeAsync()`:

```tsx
extraParams: {
  code_verifier: request.codeVerifier,
}
```

{/* prettier-ignore */}
```tsx Cognito Auth Example
import * as React from 'react';
import * as WebBrowser from 'expo-web-browser';
import { useAuthRequest, exchangeCodeAsync, revokeAsync, ResponseType } from 'expo-auth-session';
import { Button, Alert } from 'react-native';

WebBrowser.maybeCompleteAuthSession();

const clientId = '<your-client-id-here>';
const userPoolUrl =
  'https://<your-user-pool-domain>.auth.<your-region>.amazoncognito.com';
const redirectUri = 'your-redirect-uri';

export default function App() {
  const [authTokens, setAuthTokens] = React.useState(null);
  const discoveryDocument = React.useMemo(() => ({
    authorizationEndpoint: userPoolUrl + '/oauth2/authorize',
    tokenEndpoint: userPoolUrl + '/oauth2/token',
    revocationEndpoint: userPoolUrl + '/oauth2/revoke',
  }), []);

  const [request, response, promptAsync] = useAuthRequest(
    {
      clientId,
      responseType: ResponseType.Code,
      redirectUri,
      usePKCE: true,
    },
    discoveryDocument
  );

  React.useEffect(() => {
    const exchangeFn = async (exchangeTokenReq) => {
      try {
        const exchangeTokenResponse = await exchangeCodeAsync(
          exchangeTokenReq,
          discoveryDocument
        );
        setAuthTokens(exchangeTokenResponse);
      } catch (error) {
        console.error(error);
      }
    };
    if (response) {
      if (response.error) {
        Alert.alert(
          'Authentication error',
          response.params.error_description || 'something went wrong'
        );
        return;
      }
      if (response.type === 'success') {
        exchangeFn({
          clientId,
          code: response.params.code,
          redirectUri,
          extraParams: {
            code_verifier: request.codeVerifier,
          },
        });
      }
    }
  }, [discoveryDocument, request, response]);

  const logout = async () => {
    const revokeResponse = await revokeAsync(
      {
        clientId: clientId,
        token: authTokens.refreshToken,
      },
      discoveryDocument
    );
    if (revokeResponse) {
      setAuthTokens(null);
    }
  };
  console.log('authTokens: ' + JSON.stringify(authTokens));
  return authTokens ? (
    <Button title="Logout" onPress={() => logout()} />
  ) : (
    <Button disabled={!request} title="Login" onPress={() => promptAsync()} />
  );
}
```

</Box>

{/* End Cognito */}

<Box
  name="Coinbase"
  createUrl="https://www.coinbase.com/oauth/applications/new"
  image={ASSETS.coinbase}
>

| Website                       | Provider  | PKCE      | Auto Discovery |
| ----------------------------- | --------- | --------- | -------------- |
| [Get Your Config][c-coinbase] | OAuth 2.0 | Supported | Not Available  |

[c-coinbase]: https://www.coinbase.com/oauth/applications/new

- The `redirectUri` requires two slashes (`://`).
- Scopes must be joined with `:` so just create one long string.
- Setup redirect URIs: Your Project > Permitted Redirect URIs: (be sure to save after making changes).

  **Web dev**: `https://localhost:19006`

  - Run `expo start --web --https` to run with **https**, auth won't work otherwise.
  - Adding a slash to the end of the URL doesn't matter.

  **Standalone / development build**: `your-scheme://`

  - Scheme should be specified in app.json `expo.scheme: 'your-scheme'`, then added to the app code with `makeRedirectUri({ native: 'your-scheme://' })`)

  **Web production**: `https://yourwebsite.com`

  - Set this to whatever your deployed website URL is.

{/* prettier-ignore */}
```tsx Coinbase Auth Example
import {
  exchangeCodeAsync,
  makeRedirectUri,
  TokenResponse,
  useAuthRequest,
} from "expo-auth-session";
import * as WebBrowser from "expo-web-browser";
import * as React from "react";
import { Button } from "react-native";

/* @info <strong>Web only:</strong> This method should be invoked on the page that the auth popup gets redirected to on web, it'll ensure that authentication is completed properly. On native this does nothing. */
WebBrowser.maybeCompleteAuthSession();
/* @end */

// Endpoint
const discovery = {
  authorizationEndpoint: "https://www.coinbase.com/oauth/authorize",
  tokenEndpoint: "https://api.coinbase.com/oauth/token",
  revocationEndpoint: "https://api.coinbase.com/oauth/revoke",
};

const redirectUri = makeRedirectUri({ /* @info The URI <code>[scheme]://</code> to be used. If undefined, the <code>scheme</code> property of your app.json or app.config.js will be used instead. */ scheme: 'your.app'/* @end */});
const CLIENT_ID = "CLIENT_ID";

export default function App() {
  const [request, response, promptAsync] = useAuthRequest(
    {
      clientId: CLIENT_ID,
      scopes: ["wallet:accounts:read"],
      redirectUri,
    },
    discovery
  );
  const {
    // The token will be auto exchanged after auth completes.
    token,
    /* @info If the auto exchange fails, you can display the error. */
    exchangeError,
    /* @end */
  } = useAutoExchange(
    /* @info The auth code will be exchanged for an access token as soon as it's available. */
    response?.type === "success" ? response.params.code : null
    /* @end */
  );

  React.useEffect(() => {
    if (token) {
      /* @info The access token is now ready to be used to make authenticated requests. */
      console.log("My Token:", token.accessToken);
      /* @end */
    }
  }, [token]);

  return (
    <Button
      /* @info Disable the button until the request is loaded asynchronously. */
      disabled={!request}
      /* @end */
      title="Login"
      onPress={() => {
        /* @info Prompt the user to authenticate in a user interaction or web browsers will block it. */
        promptAsync();
        /* @end */
      }}
    />
  );
}

type State = {
  token: TokenResponse | null;
  exchangeError: Error | null;
};

// A hook to automatically exchange the auth token for an access token.
// this should be performed in a server and not here in the application.
// For educational purposes only:
function useAutoExchange(code?: string): State {
  const [state, setState] = React.useReducer(
    (state: State, action: Partial<State>) => ({ ...state, ...action }),
    { token: null, exchangeError: null }
  );
  const isMounted = useMounted();

  React.useEffect(() => {
    if (!code) {
      setState({ token: null, exchangeError: null });
      return;
    }

    /* @info Swap this method out for a fetch request to a server that exchanges your auth code securely. */
    exchangeCodeAsync(
      /* @end */
      {
        clientId: CLIENT_ID,
        /* @info <b>Never</b> store your client secret in the application code! */
        clientSecret: "CLIENT_SECRET",
        /* @end */
        code,
        redirectUri,
      },
      discovery
    )
      .then((token) => {
        if (isMounted.current) {
          setState({ token, exchangeError: null });
        }
      })
      .catch((exchangeError) => {
        if (isMounted.current) {
          setState({ exchangeError, token: null });
        }
      });
  }, [code]);

  return state;
}

function useMounted() {
  const isMounted = React.useRef(true);
  React.useEffect(() => {
    return () => {
      isMounted.current = false;
    };
  }, []);
  return isMounted;
}
```

</Box>
{/* End Coinbase */}

<Box
  name="Descope"
  createUrl="https://www.descope.com/sign-up"
  image={ASSETS.descope}
>

| Website                      | Provider | PKCE      | Auto Discovery |
| ---------------------------- | -------- | --------- | -------------- |
| [Get Your Config][c-descope] | OpenID   | Supported | Available      |

[c-descope]: https://docs.descope.com/

- Leverages the Hosted Descope Flow app [Auth-Hosting App](https://github.com/descope/auth-hosting).
- Requests code after successfully authenticating, followed by exchanging code for the auth tokens (PKCE).

{/* prettier-ignore */}
```tsx Descope Auth Example
import * as React from 'react';
import * as WebBrowser from 'expo-web-browser';
import * as AuthSession from 'expo-auth-session';
import { Button, View } from 'react-native';

WebBrowser.maybeCompleteAuthSession();

const descopeProjectId = '<Descope Project ID>'; // Replace this with your Descope Project ID
const descopeUrl = `https://api.descope.com/${descopeProjectId}`;
const redirectUri = AuthSession.makeRedirectUri();

export default function App() {
  const [authTokens, setAuthTokens] = React.useState(null);
  const discovery = AuthSession.useAutoDiscovery(descopeUrl);

  const [request, response, promptAsync] = AuthSession.useAuthRequest(
    {
      clientId: descopeProjectId,
      responseType: AuthSession.ResponseType.Code,
      redirectUri,
      usePKCE: true,
      scopes: ['openid', 'profile', 'email'],
    },
    discovery
  );

  React.useEffect(() => {
    if (response) {
      if (response.error) {
        console.error(
          'Authentication error',
          response.params.error_description || 'something went wrong'
        );
        return;
      }
      if (response.type === 'success') {
        /* @info Exchange the code for an access token in a server. */
        const exchangeFn = async (exchangeTokenReq) => {
        /* @end */
          try {
            const exchangeTokenResponse = await AuthSession.exchangeCodeAsync(
              exchangeTokenReq,
              discovery
            );
            setAuthTokens(exchangeTokenResponse);
          } catch (error) {
            console.error(error);
          }
        };

        exchangeFn({
          clientId: descopeProjectId,
          code: response.params.code,
          redirectUri,
          extraParams: {
            code_verifier: request.codeVerifier,
          },
        });
      }
    }
  }, [discovery, request, response]);

  const logout = async () => {
    const revokeResponse = await AuthSession.revokeAsync(
      {
        clientId: descopeProjectId,
        token: authTokens.refreshToken,
      },
      discovery
    );
    if (revokeResponse) {
      setAuthTokens(null);
    }
  };

  return (
    <View>
      {authTokens ? (
        <Button title="Logout" onPress={logout} />
      ) : (
        <Button
          /* @info Disable the button until the request is loaded asynchronously. */
          disabled={!request}
          /* @end */
          title="Login"
          /* @info Prompt the user to authenticate in a user interaction or web browsers will block it. */
          onPress={promptAsync}
          /* @end */
        />
      )}
    </View>
  );
}
```

</Box>

{/* End Descope */}

<Box
  name="Dropbox"
  createUrl="https://www.dropbox.com/developers/apps/create"
  image={ASSETS.dropbox}
>

| Website                      | Provider  | PKCE          | Auto Discovery |
| ---------------------------- | --------- | ------------- | -------------- |
| [Get Your Config][c-dropbox] | OAuth 2.0 | Not Supported | Not Available  |

[c-dropbox]: https://www.dropbox.com/developers/apps/create

- Scopes must be an empty array.

{/* prettier-ignore */}
```tsx Dropbox Auth Example
import * as React from 'react';
import * as WebBrowser from 'expo-web-browser';
import { makeRedirectUri, useAuthRequest } from 'expo-auth-session';
import { Button, Platform } from 'react-native';

/* @info <strong>Web only:</strong> This method should be invoked on the page that the auth popup gets redirected to on web, it'll ensure that authentication is completed properly. On native this does nothing. */
WebBrowser.maybeCompleteAuthSession();
/* @end */

// Endpoint
const discovery = {
  authorizationEndpoint: 'https://www.dropbox.com/oauth2/authorize',
  tokenEndpoint: 'https://www.dropbox.com/oauth2/token',
};

export default function App() {
  const [request, response, promptAsync] = useAuthRequest(
    {
      clientId: 'CLIENT_ID',
      // There are no scopes so just pass an empty array
      scopes: [],
      redirectUri: makeRedirectUri({
        /* @info The URI <code>[scheme]://</code> to be used. If undefined, the <code>scheme</code> property of your app.json or app.config.js will be used instead. */
        scheme: 'your.app',
        /* @end */
      }),
    },
    discovery
  );

  React.useEffect(() => {
    if (response?.type === 'success') {
      /* @info Exchange the code for an access token in a server. */
      const { code } = response.params;
      /* @end */
    }
  }, [response]);

  return (
    <Button
      /* @info Disable the button until the request is loaded asynchronously. */
      disabled={!request}
      /* @end */
      title="Login"
      onPress={() => {
        /* @info Prompt the user to authenticate in a user interaction or web browsers will block it. */
        promptAsync();
        /* @end */
      }}
    />
  );
}
```

</Box>
{/* End Dropbox */}

<Box
  name="Fitbit"
  createUrl="https://dev.fitbit.com/apps/new"
  image={ASSETS.fitbit}
>

| Website                     | Provider  | PKCE      | Auto Discovery |
| --------------------------- | --------- | --------- | -------------- |
| [Get Your Config][c-fitbit] | OAuth 2.0 | Supported | Not Available  |

[c-fitbit]: https://dev.fitbit.com/apps/new

- Provider only allows one redirect URI per app. You'll need an individual app for every method you want to use:
  - Standalone / development build: `com.your.app://*`
  - Web: `https://yourwebsite.com/*`
- The `redirectUri` requires two slashes (`://`).

{/* prettier-ignore */}
```tsx Fitbit Auth Example
import * as React from 'react';
import * as WebBrowser from 'expo-web-browser';
import { makeRedirectUri, useAuthRequest } from 'expo-auth-session';
import { Button, Platform } from 'react-native';

/* @info <strong>Web only:</strong> This method should be invoked on the page that the auth popup gets redirected to on web, it'll ensure that authentication is completed properly. On native this does nothing. */
WebBrowser.maybeCompleteAuthSession();
/* @end */

// Endpoint
const discovery = {
  authorizationEndpoint: 'https://www.fitbit.com/oauth2/authorize',
  tokenEndpoint: 'https://api.fitbit.com/oauth2/token',
  revocationEndpoint: 'https://api.fitbit.com/oauth2/revoke',
};

export default function App() {
  const [request, response, promptAsync] = useAuthRequest(
    {
      clientId: 'CLIENT_ID',
      scopes: ['activity', 'sleep'],
      redirectUri: makeRedirectUri({
        /* @info The URI <code>[scheme]://</code> to be used. If undefined, the <code>scheme</code> property of your app.json or app.config.js will be used instead. */
        scheme: 'your.app'
        /* @end */
      }),
    },
    discovery
  );

  React.useEffect(() => {
    if (response?.type === 'success') {
      /* @info Exchange the code for an access token in a server. */
      const { code } = response.params;
      /* @end */
    }
  }, [response]);

  return (
    <Button
      /* @info Disable the button until the request is loaded asynchronously. */
      disabled={!request}
      /* @end */
      title="Login"
      onPress={() => {
        /* @info Prompt the user to authenticate in a user interaction or web browsers will block it. */
        promptAsync();
        /* @end */
      }}
    />
  );
}
```

</Box>
{/* End FitBit */}

<Box
  name="GitHub"
  createUrl="https://github.com/settings/developers"
  image={ASSETS.github}
>

| Website                     | Provider  | PKCE      | Auto Discovery |
| --------------------------- | --------- | --------- | -------------- |
| [Get Your Config][c-github] | OAuth 2.0 | Supported | Not Available  |

[c-github]: https://github.com/settings/developers

- Provider only allows one redirect URI per app. You'll need an individual app for every method you want to use:
  - Standalone / development build: `com.your.app://*`
  - Web: `https://yourwebsite.com/*`
- The `redirectUri` requires two slashes (`://`).
- `revocationEndpoint` is dynamic and requires your `config.clientId`.

{/* prettier-ignore */}
```tsx GitHub Auth Example
import * as React from 'react';
import * as WebBrowser from 'expo-web-browser';
import { makeRedirectUri, useAuthRequest } from 'expo-auth-session';
import { Button } from 'react-native';

/* @info <strong>Web only:</strong> This method should be invoked on the page that the auth popup gets redirected to on web, it'll ensure that authentication is completed properly. On native this does nothing. */
WebBrowser.maybeCompleteAuthSession();
/* @end */

// Endpoint
const discovery = {
  authorizationEndpoint: 'https://github.com/login/oauth/authorize',
  tokenEndpoint: 'https://github.com/login/oauth/access_token',
  revocationEndpoint: 'https://github.com/settings/connections/applications/<CLIENT_ID>',
};

export default function App() {
  const [request, response, promptAsync] = useAuthRequest(
    {
      clientId: 'CLIENT_ID',
      scopes: ['identity'],
      redirectUri: makeRedirectUri({
        /* @info The URI <code>[scheme]://</code> to be used. If undefined, the <code>scheme</code> property of your app.json or app.config.js will be used instead. */
        scheme: 'your.app'
        /* @end */
      }),
    },
    discovery
  );

  React.useEffect(() => {
    if (response?.type === 'success') {
      /* @info Exchange the code for an access token in a server. */
      const { code } = response.params;
      /* @end */
    }
  }, [response]);

  return (
    <Button
      /* @info Disable the button until the request is loaded asynchronously. */
      disabled={!request}
      /* @end */
      title="Login"
      onPress={() => {
        /* @info Prompt the user to authenticate in a user interaction or web browsers will block it. */
        promptAsync();
        /* @end */
      }}
    />
  );
}
```

</Box>

<Box
  name="Imgur"
  createUrl="https://api.imgur.com/oauth2/addclient"
  image={ASSETS.imgur}
>

| Website                    | Provider  | PKCE      | Auto Discovery |
| -------------------------- | --------- | --------- | -------------- |
| [Get Your Config][c-imgur] | OAuth 2.0 | Supported | Not Available  |

[c-imgur]: https://api.imgur.com/oauth2/addclient

- You will need to create a different provider app for each platform (dynamically choosing your `clientId`).
- Learn more here: [imgur.com/oauth2](https://api.imgur.com/oauth2)

```tsx Imgur Auth Example
import * as React from 'react';
import * as WebBrowser from 'expo-web-browser';
import { makeRedirectUri, useAuthRequest } from 'expo-auth-session';
import { Button, Platform } from 'react-native';

/* @info <strong>Web only:</strong> This method should be invoked on the page that the auth popup gets redirected to on web, it'll ensure that authentication is completed properly. On native this does nothing. */
WebBrowser.maybeCompleteAuthSession();
/* @end */

const discovery = {
  authorizationEndpoint: 'https://api.imgur.com/oauth2/authorize',
  tokenEndpoint: 'https://api.imgur.com/oauth2/token',
};

export default function App() {
  // Request
  const [request, response, promptAsync] = useAuthRequest(
    {
      clientId: 'CLIENT_ID',
      clientSecret: 'CLIENT_SECRET',
      redirectUri: makeRedirectUri({
        /* @info The URI <code>[scheme]://</code> to be used. If undefined, the <code>scheme</code> property of your app.json or app.config.js will be used instead. */
        scheme: 'your.app',
        /* @end */
      }),
      // imgur requires an empty array
      scopes: [],
    },
    discovery
  );

  React.useEffect(() => {
    if (response?.type === 'success') {
      /* @info Exchange the code for an access token in a server. */
      const { code } = response.params;
      /* @end */
    }
  }, [response]);

  return (
    <Button
      /* @info Disable the button until the request is loaded asynchronously. */
      disabled={!request}
      /* @end */
      title="Login"
      onPress={() => {
        /* @info Prompt the user to authenticate in a user interaction or web browsers will block it. */
        promptAsync();
        /* @end */
      }}
    />
  );
}
```

</Box>
{/* End Imgur */}

<Box
  name="Keycloak"
  image={ASSETS.keycloak}
>

| Website | Provider | PKCE     | Auto Discovery |
| ------- | -------- | -------- | -------------- |
| -       | OpenID   | Required | Available      |

{/* prettier-ignore */}
```tsx Keycloak Auth Example
import * as React from 'react';
import * as WebBrowser from 'expo-web-browser';
import { makeRedirectUri, useAuthRequest, useAutoDiscovery } from 'expo-auth-session';
import { Button, Text, View } from 'react-native';

WebBrowser.maybeCompleteAuthSession();

export default function App() {
  /* @info If the provider supports auto discovery then you can pass an issuer to the `useAutoDiscovery` hook to fetch the discovery document. */
  const discovery = useAutoDiscovery('https://YOUR_KEYCLOAK/realms/YOUR_REALM');
  /* @end */

// Create and load an auth request
  const [request, result, promptAsync] = useAuthRequest(
    {
      clientId: 'YOUR_CLIENT_NAME',
      /* @info After a user finishes authenticating, the server will redirect them to this URI. Learn more about <a href="../../guides/linking/">linking here</a>. */
      redirectUri: makeRedirectUri({
        scheme: 'YOUR_SCHEME'
      }),
      /* @end */
      scopes: ['openid', 'profile'],
    },
    discovery
  );

  return (
    <View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
      <Button title="Login!" disabled={!request} onPress={() => promptAsync()} />
      {result && <Text>{JSON.stringify(result, null, 2)}</Text>}
    </View>
  );
}
```

</Box>
{/* End Keycloak */}

{/* Start Logto */}

<Box
  name="Logto"
  createUrl="https://docs.logto.io/quick-starts/expo/"
  image={ASSETS.logto}
>

| Website                                                     | Provider | PKCE      | Auto Discovery |
| ----------------------------------------------------------- | -------- | --------- | -------------- |
| [Get Your Config](https://docs.logto.io/quick-starts/expo/) | OpenID   | Supported | Available      |

- For native platforms, a Private-Use native URI scheme is required for `redirectUri`. See [OAuth2 spec](https://datatracker.ietf.org/doc/html/rfc8252#section-8.4) for more details.
- For web platform, a `http(s)://` scheme is required for `redirectUri`.

{/* prettier-ignore */}
```jsx Logto Auth Example
import { LogtoProvider, useLogto } from "@logto/rn";

// Use `useLogto()` hook to sign in and sign out
const Content = () => {
  const { signIn, signOut, isAuthenticated } = useLogto();

  return isAuthenticated ? (
    <Button title="Sign Out" onPress={signOut} />
  ) : (
    <Button title="Sign In" onPress={async () => signIn(redirectUri)} />
  );
};

// Wrap your page component with `LogtoProvider`
const App = () => {
  const logtoConfig = {
    appId: "YOUR_APP",
    endpoint: "YOUR_LOGTO_ENDPOINT",
  };

  return (
    <LogtoProvider config={logtoConfig}>
      <Content />
    </LogtoProvider>
  );
};
```

</Box>

{/* End Logto */}

<Box
  name="Okta"
  createUrl="https://developer.okta.com/signup"
  image={ASSETS.okta}
>

| Website                          | Provider | PKCE      | Auto Discovery |
| -------------------------------- | -------- | --------- | -------------- |
| [Sign-up][c-okta] > Applications | OpenID   | Supported | Available      |

[c-okta]: https://developer.okta.com/signup/

- You cannot define a custom `redirectUri`, Okta will provide you with one.

{/* prettier-ignore */}
```tsx Okta Auth Example
import * as React from 'react';
import * as WebBrowser from 'expo-web-browser';
import { makeRedirectUri, useAuthRequest, useAutoDiscovery } from 'expo-auth-session';
import { Button, Platform } from 'react-native';

/* @info <strong>Web only:</strong> This method should be invoked on the page that the auth popup gets redirected to on web, it'll ensure that authentication is completed properly. On native this does nothing. */
WebBrowser.maybeCompleteAuthSession();
/* @end */

export default function App() {
  // Endpoint
  const discovery = useAutoDiscovery('https://<OKTA_DOMAIN>.com/oauth2/default');
  // Request
  const [request, response, promptAsync] = useAuthRequest(
    {
      clientId: 'CLIENT_ID',
      scopes: ['openid', 'profile'],
      redirectUri: makeRedirectUri({
        native: 'com.okta.<OKTA_DOMAIN>:/callback',
      }),
    },
    discovery
  );

  React.useEffect(() => {
    if (response?.type === 'success') {
      /* @info Exchange the code for an access token in a server. */
      const { code } = response.params;
      /* @end */
    }
  }, [response]);

  return (
    <Button
      /* @info Disable the button until the request is loaded asynchronously. */
      disabled={!request}
      /* @end */
      title="Login"
      onPress={() => {
        /* @info Prompt the user to authenticate in a user interaction or web browsers will block it. */
        promptAsync();
        /* @end */
      }}
    />
  );
}
```

</Box>
{/* End Okta */}

<Box
  name="Reddit"
  createUrl="https://www.reddit.com/prefs/apps"
  image={ASSETS.reddit}
>

| Website                     | Provider  | PKCE      | Auto Discovery |
| --------------------------- | --------- | --------- | -------------- |
| [Get Your Config][c-reddit] | OAuth 2.0 | Supported | Not Available  |

[c-reddit]: https://www.reddit.com/prefs/apps

- Provider only allows one redirect URI per app. You'll need an individual app for every method you want to use:
  - Standalone / development build: `com.your.app://*`
  - Web: `https://yourwebsite.com/*`
- The `redirectUri` requires two slashes (`://`).

{/* prettier-ignore */}
```tsx Reddit Auth Example
import * as React from 'react';
import * as WebBrowser from 'expo-web-browser';
import { makeRedirectUri, useAuthRequest } from 'expo-auth-session';
import { Button } from 'react-native';

/* @info <strong>Web only:</strong> This method should be invoked on the page that the auth popup gets redirected to on web, it'll ensure that authentication is completed properly. On native this does nothing. */
WebBrowser.maybeCompleteAuthSession();
/* @end */

// Endpoint
const discovery = {
  authorizationEndpoint: 'https://www.reddit.com/api/v1/authorize.compact',
  tokenEndpoint: 'https://www.reddit.com/api/v1/access_token',
};

export default function App() {
  const [request, response, promptAsync] = useAuthRequest(
    {
      clientId: 'CLIENT_ID',
      scopes: ['identity'],
      redirectUri: makeRedirectUri({
        native: 'your.app://redirect',
      }),
    },
    discovery
  );

  React.useEffect(() => {
    if (response?.type === 'success') {
      /* @info Exchange the code for an access token in a server. */
      const { code } = response.params;
      /* @end */
    }
  }, [response]);

  return (
    <Button
      /* @info Disable the button until the request is loaded asynchronously. */
      disabled={!request}
      /* @end */
      title="Login"
      onPress={() => {
        /* @info Prompt the user to authenticate in a user interaction or web browsers will block it. */
        promptAsync();
        /* @end */
      }}
    />
  );
}
```

</Box>
{/* End Reddit */}

<Box
  name="Slack"
  createUrl="https://api.slack.com/apps"
  image={ASSETS.slack}
>

| Website                    | Provider  | PKCE      | Auto Discovery |
| -------------------------- | --------- | --------- | -------------- |
| [Get Your Config][c-slack] | OAuth 2.0 | Supported | Not Available  |

[c-slack]: https://api.slack.com/apps

- The `redirectUri` requires two slashes (`://`).
- `redirectUri` can be defined under the "OAuth & Permissions" section of the website.
- `clientId` and `clientSecret` can be found in the **"App Credentials"** section.
- Scopes must be joined with ':' so just create one long string.
- Navigate to the **"Scopes"** section to enable scopes.
- `revocationEndpoint` is not available.

{/* prettier-ignore */}
```tsx Slack Auth Example
import * as React from 'react';
import * as WebBrowser from 'expo-web-browser';
import { makeRedirectUri, useAuthRequest } from 'expo-auth-session';
import { Button } from 'react-native';

/* @info <strong>Web only:</strong> This method should be invoked on the page that the auth popup gets redirected to on web, it'll ensure that authentication is completed properly. On native this does nothing. */
WebBrowser.maybeCompleteAuthSession();
/* @end */

// Endpoint
const discovery = {
  authorizationEndpoint: 'https://slack.com/oauth/authorize',
  tokenEndpoint: 'https://slack.com/api/oauth.access',
};

export default function App() {
  const [request, response, promptAsync] = useAuthRequest(
    {
      clientId: 'CLIENT_ID',
      scopes: ['emoji:read'],
      redirectUri: makeRedirectUri({
        /* @info The URI <code>[scheme]://</code> to be used. If undefined, the <code>scheme</code> property of your app.json or app.config.js will be used instead. */
        scheme: 'your.app'
        /* @end */
      }),
    },
    discovery
  );

  React.useEffect(() => {
    if (response?.type === 'success') {
      /* @info Exchange the code for an access token in a server. */
      const { code } = response.params;
      /* @end */
    }
  }, [response]);

  return (
    <Button
      /* @info Disable the button until the request is loaded asynchronously. */
      disabled={!request}
      /* @end */
      title="Login"
      onPress={() => {
        /* @info Prompt the user to authenticate in a user interaction or web browsers will block it. */
        promptAsync();
        /* @end */
      }}
    />
  );
}
```

</Box>
{/* End Slack */}

<Box
  name="Spotify"
  createUrl="https://developer.spotify.com/dashboard/applications"
  image={ASSETS.spotify}
>

| Website                      | Provider  | PKCE      | Auto Discovery |
| ---------------------------- | --------- | --------- | -------------- |
| [Get Your Config][c-spotify] | OAuth 2.0 | Supported | Not Available  |

[c-spotify]: https://developer.spotify.com/dashboard/applications

- Setup your redirect URIs: **Your project > Edit Settings > Redirect URIs**, be sure to save after making changes.

  **Web dev**: `https://localhost:19006`

  - Important: Ensure there's no slash at the end of the URL unless manually changed in the app code with `makeRedirectUri({ path: '/' })`.
  - Run `expo start --web --https` to run with **https**, auth won't work otherwise.

  **Standalone / development build**: `your-scheme://`

  - Scheme should be specified in app.json `expo.scheme: 'your-scheme'`, then added to the app code with `makeRedirectUri({ native: 'your-scheme://' })`)

  **Web production**: `https://yourwebsite.com`

  - Set this to whatever your deployed website URL is.

- Learn more about the [Spotify API](https://developer.spotify.com/documentation/web-api/).

{/* prettier-ignore */}
```tsx Spotify Auth Example
import * as React from 'react';
import * as WebBrowser from 'expo-web-browser';
import { makeRedirectUri, useAuthRequest } from 'expo-auth-session';
import { Button } from 'react-native';

/* @info <strong>Web only:</strong> This method should be invoked on the page that the auth popup gets redirected to on web, it'll ensure that authentication is completed properly. On native this does nothing. */
WebBrowser.maybeCompleteAuthSession();
/* @end */

// Endpoint
const discovery = {
  authorizationEndpoint: 'https://accounts.spotify.com/authorize',
  tokenEndpoint: 'https://accounts.spotify.com/api/token',
};

export default function App() {
  const [request, response, promptAsync] = useAuthRequest(
    {
      clientId: 'CLIENT_ID',
      scopes: ['user-read-email', 'playlist-modify-public'],
      // To follow the "Authorization Code Flow" to fetch token after authorizationEndpoint
      // this must be set to false
      usePKCE: false,
      redirectUri: makeRedirectUri({
        /* @info The URI <code>[scheme]://</code> to be used. If undefined, the <code>scheme</code> property of your app.json or app.config.js will be used instead. */
        scheme: 'your.app'
        /* @end */
      }),
    },
    discovery
  );

  React.useEffect(() => {
    if (response?.type === 'success') {
      /* @info Exchange the code for an access token in a server. */
      const { code } = response.params;
      /* @end */
    }
  }, [response]);

  return (
    <Button
      /* @info Disable the button until the request is loaded asynchronously. */
      disabled={!request}
      /* @end */
      title="Login"
      onPress={() => {
        /* @info Prompt the user to authenticate in a user interaction or web browsers will block it. */
        promptAsync();
        /* @end */
      }}
    />
  );
}
```

</Box>
{/* End Spotify */}

<Box
  name="Strava"
  createUrl="https://www.strava.com/settings/api"
  image={ASSETS.strava}
>

| Website                     | Provider  | PKCE      | Auto Discovery |
| --------------------------- | --------- | --------- | -------------- |
| [Get Your Config][c-strava] | OAuth 2.0 | Supported | Not Available  |

[c-strava]: https://www.strava.com/settings/api

- Learn more about the [Strava API](http://developers.strava.com/docs/reference/).
- The "Authorization Callback Domain" refers to the final path component of your redirect URI. Ex: In the URI `com.bacon.myapp://redirect` the domain would be `redirect`.
- No Implicit auth flow is provided by Strava.

{/* prettier-ignore */}
```tsx Strava Auth Example
import * as React from 'react';
import * as WebBrowser from 'expo-web-browser';
import { makeRedirectUri, useAuthRequest } from 'expo-auth-session';
import { Button } from 'react-native';

/* @info <strong>Web only:</strong> This method should be invoked on the page that the auth popup gets redirected to on web, it'll ensure that authentication is completed properly. On native this does nothing. */
WebBrowser.maybeCompleteAuthSession();
/* @end */

// Endpoint
const discovery = {
  authorizationEndpoint: 'https://www.strava.com/oauth/mobile/authorize',
  tokenEndpoint: 'https://www.strava.com/oauth/token',
  revocationEndpoint: 'https://www.strava.com/oauth/deauthorize',
};

export default function App() {
  const [request, response, promptAsync] = useAuthRequest(
    {
      clientId: 'CLIENT_ID',
      scopes: ['activity:read_all'],
      redirectUri: makeRedirectUri({
        // the "redirect" must match your "Authorization Callback Domain" in the Strava dev console.
        native: 'your.app://redirect',
      }),
    },
    discovery
  );

  React.useEffect(() => {
    if (response?.type === 'success') {
      /* @info Exchange the code for an access token in a server. */
      const { code } = response.params;
      /* @end */
    }
  }, [response]);

  return (
    <Button
      /* @info Disable the button until the request is loaded asynchronously. */
      disabled={!request}
      /* @end */
      title="Login"
      onPress={() => {
        /* @info Prompt the user to authenticate in a user interaction or web browsers will block it. */
        promptAsync();
        /* @end */
      }}
    />
  );
}
```

Strava doesn't provide an implicit auth flow, you should send the code to a server or serverless function to perform the access token exchange.
For **debugging** purposes, you can perform the exchange client-side using the following method:

{/* prettier-ignore */}
```tsx
const { accessToken } = await AuthSession.exchangeCodeAsync(
  {
    clientId: request?.clientId,
    redirectUri,
    code: result.params.code,
    extraParams: {
      // You must use the extraParams variation of clientSecret.
      // Never store your client secret on the client.
      client_secret: 'CLIENT_SECRET',
    },
  },
  { tokenEndpoint: 'https://www.strava.com/oauth/token' }
);
```

</Box>
{/* End Strava */}

<Box
  name="Strivacity"
  createUrl="https://www.strivacity.com/learn-support/developer-hub"
  image={ASSETS.strivacity}
>

| Website                                                                      | Provider | PKCE      | Auto Discovery |
| ---------------------------------------------------------------------------- | -------- | --------- | -------------- |
| [Get Your Config](https://docs.strivacity.com/docs/setting-up-your-instance) | OpenID   | Supported | Available      |

{/* prettier-ignore */}
```tsx Strivacity Auth Example
import * as React from 'react';
import * as WebBrowser from 'expo-web-browser';
import { makeRedirectUri, useAuthRequest } from 'expo-auth-session';
import { Button } from 'react-native';

/* @info <strong>Web only:</strong> This method should be invoked on the page that the auth popup gets redirected to on web. It will ensure that authentication is completed properly. On native platforms, this does nothing. */
WebBrowser.maybeCompleteAuthSession();
/* @end */

// Endpoint
const discovery = {
  authorizationEndpoint: 'https://<instance>.strivacity.com/oauth2/auth',
  tokenEndpoint: 'https://<instance>.strivacity.com/oauth2/token',
  revocationEndpoint: 'https://<instance>.strivacity.com/oauth2/revoke',
};

export default function App() {
  const [request, response, promptAsync] = useAuthRequest(
    {
      clientId: 'CLIENT_ID',
      scopes: ['openid', 'profile'],
      redirectUri: makeRedirectUri();
    },
    discovery
  );

  React.useEffect(() => {
    if (response?.type === 'success') {
      /* @info Exchange the code for an access token in a server. */
      const { code } = response.params;
      /* @end */
    }
  }, [response]);

  return (
    <Button
      /* @info Disable the button until the request is loaded asynchronously. */
      disabled={!request}
      /* @end */
      title="Login"
      onPress={() => {
        /* @info Prompt the user to authenticate in a user interaction or web browsers will block it. */
        promptAsync();
        /* @end */
      }}
    />
  );
}
```

</Box>
{/* End Strivacity */}

<Box
  name="Twitch"
  createUrl="https://dev.twitch.tv/console/apps/create"
  image={ASSETS.twitch}
>

| Website                     | Provider | PKCE      | Auto Discovery | Scopes           |
| --------------------------- | -------- | --------- | -------------- | ---------------- |
| [Get your Config][c-twitch] | OAuth    | Supported | Not Available  | [Info][s-twitch] |

[c-twitch]: https://dev.twitch.tv/console/apps/create
[s-twitch]: https://dev.twitch.tv/docs/authentication#scopes

- You will need to enable 2FA on your Twitch account to create an application.

{/* prettier-ignore */}
```tsx Twitch Auth Example
import * as React from 'react';
import * as WebBrowser from 'expo-web-browser';
import { makeRedirectUri, useAuthRequest } from 'expo-auth-session';
import { Button } from 'react-native';

/* @info <strong>Web only:</strong> This method should be invoked on the page that the auth popup gets redirected to on web, it'll ensure that authentication is completed properly. On native this does nothing. */
WebBrowser.maybeCompleteAuthSession();
/* @end */

// Endpoint
const discovery = {
  authorizationEndpoint: 'https://id.twitch.tv/oauth2/authorize',
  tokenEndpoint: 'https://id.twitch.tv/oauth2/token',
  revocationEndpoint: 'https://id.twitch.tv/oauth2/revoke',
};

export default function App() {
  const [request, response, promptAsync] = useAuthRequest(
    {
      clientId: 'CLIENT_ID',
      redirectUri: makeRedirectUri({
        /* @info The URI <code>[scheme]://</code> to be used. If undefined, the <code>scheme</code> property of your app.json or app.config.js will be used instead. */
        scheme: 'your.app'
        /* @end */
      }),
      scopes: ['user:read:email', 'analytics:read:games'],
    },
    discovery
  );

  React.useEffect(() => {
    if (response?.type === 'success') {
      /* @info Exchange the code for an access token in a server. */
      const { code } = response.params;
      /* @end */
    }
  }, [response]);

  return (
    <Button
      /* @info Disable the button until the request is loaded asynchronously. */
      disabled={!request}
      /* @end */
      title="Login"
      onPress={() => {
        /* @info Prompt the user to authenticate in a user interaction or web browsers will block it. */
        promptAsync();
        /* @end */
      }}
    />
  );
}
```

</Box>
{/* End Twitch */}

<Box
  name="Twitter"
  createUrl="https://developer.twitter.com/en/portal/projects/new"
  image={ASSETS.twitter}
>

| Website                      | Provider | PKCE      | Auto Discovery | Scopes            |
| ---------------------------- | -------- | --------- | -------------- | ----------------- |
| [Get your Config][c-twitter] | OAuth    | Supported | Not Available  | [Info][s-twitter] |

[c-twitter]: https://developer.twitter.com/en/portal/projects/new
[s-twitter]: https://developer.twitter.com/en/docs/authentication/oauth-2-0/authorization-code

- You will need to be approved by Twitter support before you can use the Twitter v2 API.
- Web does not appear to work, the Twitter authentication website appears to block the popup, causing the `response` of `useAuthRequest` to always be `{type: 'dismiss'}`.
- Example redirects:
  - Standalone / development build: `com.your.app://`
  - Web (dev `expo start --https`): `https://localhost:19006` (no ending slash)
- The `redirectUri` requires two slashes (`://`).

{/* prettier-ignore */}
```tsx Twitter Auth Example
import * as React from 'react';
import * as WebBrowser from 'expo-web-browser';
import { makeRedirectUri, useAuthRequest } from 'expo-auth-session';
import { Button, Platform } from 'react-native';

/* @info <strong>Web only:</strong> This method should be invoked on the page that the auth popup gets redirected to on web, it'll ensure that authentication is completed properly. On native this does nothing. */
WebBrowser.maybeCompleteAuthSession();
/* @end */

// Endpoint
const discovery = {
  authorizationEndpoint: "https://twitter.com/i/oauth2/authorize",
  tokenEndpoint: "https://twitter.com/i/oauth2/token",
  revocationEndpoint: "https://twitter.com/i/oauth2/revoke",
};

export default function App() {
  const [request, response, promptAsync] = useAuthRequest(
    {
      clientId: 'CLIENT_ID',
      redirectUri: makeRedirectUri({
        /* @info The URI <code>[scheme]://</code> to be used. If undefined, the <code>scheme</code> property of your app.json or app.config.js will be used instead. */
        scheme: 'your.app',
        /* @end */
      }),
      usePKCE: true,
      scopes: [
        "tweet.read",
      ],
    },
    discovery
  );

  React.useEffect(() => {
    if (response?.type === 'success') {
      /* @info Exchange the code for an access token in a server. */
      const { code } = response.params;
      /* @end */
    }
  }, [response]);

  return (
    <Button
      /* @info Disable the button until the request is loaded asynchronously. */
      disabled={!request}
      /* @end */
      title="Login"
      onPress={() => {
        /* @info Prompt the user to authenticate in a user interaction or web browsers will block it. */
        promptAsync();
        /* @end */
      }}
    />
  );
}
```

</Box>
{/* End Twitter */}

<Box
  name="Uber"
  createUrl="https://developer.uber.com/docs/riders/guides/authentication/introduction"
  image={ASSETS.uber}
>

| Website                   | Provider  | PKCE      | Auto Discovery |
| ------------------------- | --------- | --------- | -------------- |
| [Get Your Config][c-uber] | OAuth 2.0 | Supported | Not Available  |

[c-uber]: https://developer.uber.com/docs/riders/guides/authentication/introduction

- The `redirectUri` requires two slashes (`://`).
- `scopes` can be difficult to get approved.

{/* prettier-ignore */}
```tsx Uber Auth Example
import * as React from 'react';
import * as WebBrowser from 'expo-web-browser';
import { makeRedirectUri, useAuthRequest } from 'expo-auth-session';
import { Button } from 'react-native';

/* @info <strong>Web only:</strong> This method should be invoked on the page that the auth popup gets redirected to on web, it'll ensure that authentication is completed properly. On native this does nothing. */
WebBrowser.maybeCompleteAuthSession();
/* @end */

// Endpoint
const discovery = {
  authorizationEndpoint: 'https://login.uber.com/oauth/v2/authorize',
  tokenEndpoint: 'https://login.uber.com/oauth/v2/token',
  revocationEndpoint: 'https://login.uber.com/oauth/v2/revoke',
};

export default function App() {
  const [request, response, promptAsync] = useAuthRequest(
    {
      clientId: 'CLIENT_ID',
      scopes: ['profile', 'delivery'],
      redirectUri: makeRedirectUri({
        /* @info The URI <code>[scheme]://</code> to be used. If undefined, the <code>scheme</code> property of your app.json or app.config.js will be used instead. */
        scheme: 'your.app'
        /* @end */
      }),
    },
    discovery
  );

  React.useEffect(() => {
    if (response?.type === 'success') {
      /* @info Exchange the code for an access token in a server. */
      const { code } = response.params;
      /* @end */
    }
  }, [response]);

  return (
    <Button
      /* @info Disable the button until the request is loaded asynchronously. */
      disabled={!request}
      /* @end */
      title="Login"
      onPress={() => {
        /* @info Prompt the user to authenticate in a user interaction or web browsers will block it. */
        promptAsync();
        /* @end */
      }}
    />
  );
}
```

</Box>
{/* End Uber */}

{/* End Guides */}

## Redirect URI patterns

Here are a few examples of some common redirect URI patterns you may end up using.

### Standalone / development build

> `yourscheme://path`

In some cases there will be anywhere between 1 to 3 slashes (`/`).

- **Environment:**

  - Bare workflow
    - `npx expo prebuild`
  - Standalone builds in the App or Play Store or testing locally
    - Android: `eas build` or `npx expo run:android`
    - iOS: `eas build` or `npx expo run:ios`

- **Create:** Use `AuthSession.makeRedirectUri({ native: '<YOUR_URI>' })` to select native when running in the correct environment.
  - `your.app://redirect` -> `makeRedirectUri({ scheme: 'your.app', path: 'redirect' })`
  - `your.app:///` -> `makeRedirectUri({ scheme: 'your.app', isTripleSlashed: true })`
  - `your.app:/authorize` -> `makeRedirectUri({ native: 'your.app:/authorize' })`
  - `your.app://auth?foo=bar` -> `makeRedirectUri({ scheme: 'your.app', path: 'auth', queryParams: { foo: 'bar' } })`
  - `exp://u.expo.dev/[project-id]?channel-name=[channel-name]&runtime-version=[runtime-version]` -> `makeRedirectUri()`
  - This link can often be created automatically but we recommend you define the `scheme` property at least. The entire URL can be overridden in apps by passing the `native` property. Often this will be used for providers like Google or Okta which require you to use a custom native URI redirect. You can add, list, and open URI schemes using `npx uri-scheme`.
  - If you change the `expo.scheme` after ejecting then you'll need to use the `expo apply` command to apply the changes to your native project, then rebuild them (`yarn ios`, `yarn android`).
- **Usage:** `promptAsync({ redirectUri })`

## Improving user experience

The "login flow" is an important thing to get right, in a lot of cases this is where the user will _commit_ to using your app again. A bad experience can cause users to give up on your app before they've really gotten to use it.

Here are a few tips you can use to make authentication quick, easy, and secure for your users!

### Warming the browser

On Android you can optionally warm up the web browser before it's used. This allows the browser app to pre-initialize itself in the background. Doing this can significantly speed up prompting the user for authentication.

{/* prettier-ignore */}
```tsx
import * as React from 'react';
import * as WebBrowser from 'expo-web-browser';

function App() {
  React.useEffect(() => {
    /* @info <strong>Android only:</strong> Start loading the default browser app in the background to improve transition time. */
    WebBrowser.warmUpAsync();
    /* @end */

    return () => {
      /* @info <strong>Android only:</strong> Cool down the browser when the component unmounts to help improve memory on low-end Android devices. */
      WebBrowser.coolDownAsync();
      /* @end */
    };
  }, []);

  // Do authentication ...
}
```

### Implicit login

Because there was no secure way to do this to store client secrets in your app bundle, historically, many providers have offered an "Implicit flow" which enables you to request an access token without the client secret. **This is no longer recommended** due to inherent security risks, including the risk of access token injection. Instead, most providers now support the authorization code with PKCE (Proof Key for Code Exchange) extension to securely exchange an authorization code for an access token within your client app code. Learn more about [transitioning from Implicit flow to authorization code with PKCE](https://oauth.net/2/grant-types/implicit/).

`expo-auth-session` still supports Implicit flow for legacy code purposes. Below is an example implementation of the Implicit flow.

{/* prettier-ignore */}
```tsx
import * as React from 'react';
import * as WebBrowser from 'expo-web-browser';
import { makeRedirectUri, useAuthRequest, ResponseType } from 'expo-auth-session';

/* @info <strong>Web only:</strong> This method should be invoked on the page that the auth popup gets redirected to on web, it'll ensure that authentication is completed properly. On native this does nothing. */
WebBrowser.maybeCompleteAuthSession();
/* @end */

// Endpoint
const discovery = {
  authorizationEndpoint: 'https://accounts.spotify.com/authorize',
};

function App() {
  const [request, response, promptAsync] = useAuthRequest(
    {
      /* @info Request that the server returns an <code>access_token</code>, not all providers support this. */
      responseType: ResponseType.Token,
      /* @end */
      clientId: 'CLIENT_ID',
      scopes: ['user-read-email', 'playlist-modify-public'],
      redirectUri: makeRedirectUri({
        /* @info The URI <code>[scheme]://</code> to be used. If undefined, the <code>scheme</code> property of your app.json or app.config.js will be used instead. */
        scheme: 'your.app'
        /* @end */
      }),
    },
    discovery
  );

  React.useEffect(() => {
    if (response && response.type === 'success') {
      /* @info You can use this access token to make calls into the Spotify API. */
      const token = response.params.access_token;
      /* @end */
    }
  }, [response]);

  return <Button disabled={!request} onPress={() => promptAsync()} title="Login" />;
}
```

### Storing data

On native platforms like iOS, and Android you can secure things like access tokens locally using a package called [`expo-secure-store`](/versions/latest/sdk/securestore) (This is different to `AsyncStorage` which is not secure). This package provides native access to [keychain services](https://developer.apple.com/documentation/security/keychain_services) on iOS and encrypted [`SharedPreferences`](https://developer.android.com/training/basics/data-storage/shared-preferences.html) on Android. There is no web equivalent to this functionality.

You can store your authentication results and rehydrate them later to avoid having to prompt the user to login again.

{/* prettier-ignore */}
```tsx
import * as SecureStore from 'expo-secure-store';

const MY_SECURE_AUTH_STATE_KEY = 'MySecureAuthStateKey';

function App() {
  const [, response] = useAuthRequest({});

  React.useEffect(() => {
    if (response && response.type === 'success') {
      const auth = response.params;
      const storageValue = JSON.stringify(auth);

      if (Platform.OS !== 'web') {
        // Securely store the auth on your device
        SecureStore.setItemAsync(MY_SECURE_AUTH_STATE_KEY, storageValue);
      }
    }
  }, [response]);

  // More login code...
}
```
